package com.ruoyi.flow.service.de.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.flow.mapper.de.ProdDeModelMapper;
import com.ruoyi.flow.model.de.ProdDeModel;
import com.ruoyi.flow.service.de.ProdDeModelService;
import com.ruoyi.flow.tools.pager.PagerModel;
import com.ruoyi.flow.tools.pager.Query;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import org.flowable.common.engine.impl.util.io.InputStreamSource;
import org.flowable.dmn.api.DmnDecision;
import org.flowable.dmn.api.DmnDecisionQuery;
import org.flowable.dmn.api.DmnRepositoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

/**
 * @title:
 * @author: bruce.liu
 * @since: 2023/5/9 23:45
 */
@Service
@Transactional
public class ProdDeModelServiceImpl extends ServiceImpl<ProdDeModelMapper, ProdDeModel> implements ProdDeModelService {
    @Autowired
    private ProdDeModelMapper prodDeModelMapper;
    @Autowired
    private DmnRepositoryService dmnRepositoryService;
    BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();

    @Override
    public void updateProcessNameExpByKey(String modelKey, String processNameExp) {
        if (StringUtils.isNotBlank(modelKey) && StringUtils.isNotBlank(processNameExp)) {
            LambdaUpdateWrapper<ProdDeModel> prodDeModelLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            prodDeModelLambdaUpdateWrapper.set(ProdDeModel::getProcessNameExp, processNameExp).eq(ProdDeModel::getModelKey, modelKey);
            this.update(prodDeModelLambdaUpdateWrapper);
        }
    }

    @Override
    public long getFlowCountByCategoryCodes(List<String> categoryCodes) {
        long count = 0;
        if (CollectionUtils.isNotEmpty(categoryCodes)) {
            LambdaQueryWrapper<ProdDeModel> prodDeModelLambdaQueryWrapper = new LambdaQueryWrapper<>();
            prodDeModelLambdaQueryWrapper.in(ProdDeModel::getCategory, categoryCodes);
            count = this.count(prodDeModelLambdaQueryWrapper);
        }
        return count;
    }

    @Override
    @Transactional(readOnly = true)
    public Integer getDeModelCount(String modelKey) {
        return prodDeModelMapper.getDeModelCount(modelKey);
    }

    @Override
    public ProdDeModel duplicateModel(String id, ProdDeModel newModel) {
        String xml = null;
        ProdDeModel model = null;
        if (id != null) {
            model = this.getById(id);
            xml = model.getModelEditorXml();
        }
        if (model == null) {
            throw new RuntimeException("Error duplicating model : Unknown original model");
        } else {
            newModel.setModelKey(newModel.getModelKey().replaceAll(" ", ""));
            boolean validateModelKey = this.validateModelKey(ProdDeModel.MODEL_TYPE_BPMN, newModel.getModelKey());
            if (validateModelKey) {
                throw new RuntimeException("key 重复");
            }
        }
        String bpmnXML = null;
        if (newModel.getModelType() == null || newModel.getModelType().equals(ProdDeModel.MODEL_TYPE_BPMN)) {
            BpmnModel bpmnModel = this.getBpmnModelByXml(xml);
            bpmnModel.getMainProcess().setId(newModel.getModelKey());
            bpmnModel.getMainProcess().setName(newModel.getName());
            bpmnXML = this.getBpmnXML(bpmnModel);
            newModel.setModelEditorXml(bpmnXML);
        }
        newModel.setCategory(model.getCategory());
        newModel.setTenantId(model.getTenantId());
        newModel.setVersion(0);
        newModel.setCreated(new Date());
        this.saveOrUpdate(newModel);
        return newModel;
    }

    @Override
    @Transactional(readOnly = true)
    public List<ProdDeModel> getModelsByModelType(Integer modelType) {
        LambdaQueryWrapper<ProdDeModel> queryWrapper = new LambdaQueryWrapper<>();
        if (modelType != null) {
            queryWrapper.eq(ProdDeModel::getModelType, modelType);
            return this.list(queryWrapper);
        }
        return null;
    }

    @Override
    @Transactional(readOnly = true)
    public Page<ProdDeModel> queryPagerModel(ProdDeModel model, Page page) {
        LambdaQueryWrapper<ProdDeModel> queryWrapper = new LambdaQueryWrapper<>();
        if (model != null) {
            queryWrapper.like(StringUtils.isNotBlank(model.getName()), ProdDeModel::getName, model.getName());
            queryWrapper.eq(StringUtils.isNotBlank(model.getModelKey()), ProdDeModel::getModelKey, model.getModelKey());
        }
        return this.baseMapper.selectPage(page, queryWrapper);
    }


    @Override
    @Transactional(readOnly = true)
    public PagerModel<ProdDeModel> queryPagerModel(ProdDeModel model, Query query) {
        if (model == null) {
            model = new ProdDeModel();
            model.setModelType(ProdDeModel.MODEL_TYPE_BPMN);
        }
        if (query == null) {
            query = new Query();
        }
        Page<ProdDeModel> page = new Page<>(query.getPageNum(), query.getPageSize());
        IPage<ProdDeModel> ipage = prodDeModelMapper.queryPagerModel(model, page);
        PagerModel<ProdDeModel> pagerModel = new PagerModel<>();
        if (null != ipage && CollectionUtils.isNotEmpty(ipage.getRecords())) {
            for (ProdDeModel prodDeModel : ipage.getRecords()) {
                if (ProdDeModel.MODEL_TYPE_BPMN == model.getModelType()) {
                } else if (ProdDeModel.MODEL_TYPE_DECISION_TABLE == model.getModelType()) {
                    DmnDecisionQuery dmnDecisionQuery = dmnRepositoryService.createDecisionQuery().latestVersion().decisionKey(prodDeModel.getModelKey());
                    if (StringUtils.isNotBlank(prodDeModel.getTenantId())) {
                        dmnDecisionQuery.decisionTenantId(prodDeModel.getTenantId());
                    }
                    DmnDecision decision = dmnDecisionQuery.singleResult();
                    if (decision != null) {
                        prodDeModel.setVersion(decision.getVersion());
                    } else {
                        prodDeModel.setVersion(0);
                    }
                }
            }
            pagerModel = new PagerModel<>(ipage.getTotal(), ipage.getRecords());
        }
        return pagerModel;
    }

    @Override
    @Transactional(readOnly = true)
    public ProdDeModel getBpmnModelByKey(String key) {
        return getModelByKey(ProdDeModel.MODEL_TYPE_BPMN, key);
    }

    @Override
    @Transactional(readOnly = true)
    public ProdDeModel getDmnModelByKey(String key) {
        return getModelByKey(ProdDeModel.MODEL_TYPE_DECISION_TABLE, key);
    }

    @Override
    @Transactional(readOnly = true)
    public ProdDeModel getModelByKey(Integer modelType, String key) {
        LambdaQueryWrapper<ProdDeModel> prodDeModelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        prodDeModelLambdaQueryWrapper.eq(ProdDeModel::getModelType, modelType).eq(ProdDeModel::getModelKey, key).orderByDesc(ProdDeModel::getVersion).last("limit 1");
        return getOne(prodDeModelLambdaQueryWrapper);
    }

    @Override
    @Transactional(readOnly = true)
    public ProdDeModel getModelByIdOrKey(Integer modelType, String id, String key) {
        LambdaQueryWrapper<ProdDeModel> prodDeModelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        prodDeModelLambdaQueryWrapper.eq(ProdDeModel::getModelType, modelType).eq(ProdDeModel::getModelKey, key).or().eq(ProdDeModel::getId, id).orderByDesc(ProdDeModel::getVersion).last("limit 1");
        return getOne(prodDeModelLambdaQueryWrapper);
    }

    @Override
    @Transactional(readOnly = true)
    public boolean validateModelKey(Integer modelType, String modelKey) {
        boolean flag = false;
        if (modelType != null && StringUtils.isNotBlank(modelKey)) {
            LambdaQueryWrapper<ProdDeModel> prodDeModelLambdaQueryWrapper = new LambdaQueryWrapper<>();
            prodDeModelLambdaQueryWrapper.eq(ProdDeModel::getModelType, modelType).eq(ProdDeModel::getModelKey, modelKey).orderByDesc(ProdDeModel::getVersion).last("limit 1");
            ;
            long count = this.count(prodDeModelLambdaQueryWrapper);
            if (count > 0) {
                flag = true;
            }
        }
        return flag;
    }


    @Override
    @Transactional(readOnly = true)
    public BpmnModel getBpmnModelByXml(String xml) {
        return bpmnXMLConverter.convertToBpmnModel(new BytesStreamSource(xml.getBytes()), false, false);
    }

    @Override
    @Transactional(readOnly = true)
    public byte[] getBpmnXMLByte(BpmnModel bpmnModel) {
        for (Process process : bpmnModel.getProcesses()) {
            if (StringUtils.isNotEmpty(process.getId())) {
                char firstCharacter = process.getId().charAt(0);
                if (Character.isDigit(firstCharacter)) {
                    process.setId("a" + process.getId());
                }
            }
        }
        byte[] xmlBytes = bpmnXMLConverter.convertToXML(bpmnModel);
        return xmlBytes;
    }

    public BpmnModel exportAndReadXMLFile(BpmnModel model) {
        byte[] xml = bpmnXMLConverter.convertToXML(model);
        return bpmnXMLConverter.convertToBpmnModel(new InputStreamSource(new ByteArrayInputStream(xml)), true, false, "UTF-8");
    }

    @Override
    @Transactional(readOnly = true)
    public String getBpmnXML(BpmnModel bpmnModel) {
        byte[] byteXml = this.getBpmnXMLByte(bpmnModel);
        String xml = null;
        try {
            xml = new String(byteXml, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return xml;
    }

    @Override
    public void updateStatusById(String id, String status) {
        LambdaUpdateWrapper<ProdDeModel> prodDeModelLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        prodDeModelLambdaUpdateWrapper.set(ProdDeModel::getStatus, status).eq(ProdDeModel::getId, id);
        this.update(prodDeModelLambdaUpdateWrapper);
    }
}
